package com.simulation.service;

import com.simulation.model.SimulationConfig;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;

import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;

//import javax.annotation.PostConstruct;
//import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Service
@DependsOn("configurationService")
public class OpcuaServerService implements DisposableBean, ApplicationRunner {
    private static final Logger logger = LoggerFactory.getLogger(OpcuaServerService.class);
    
    private OpcUaServer server;
    private final Map<String, NodeId> nodeIdCache = new ConcurrentHashMap<>();
    
    @Autowired
    private ConfigurationService configurationService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initialize();
    }

//    @PostConstruct
    public void initialize() {
        try {
            SimulationConfig config = configurationService.getConfig();
            
            // 创建OPC UA服务器配置
            OpcUaServerConfig serverConfig = OpcUaServerConfig.builder()
                .setApplicationName(LocalizedText.english("Data Simulation Server"))
                .setApplicationUri("urn:simulation:server")
                .setProductUri("urn:simulation:product")
                .setBuildInfo(new BuildInfo(
                    "urn:simulation:product",
                    "Data Simulation Server", 
                    "1.0.0",
                    "1.0.0",
                    "1.0.0",
                    DateTime.now()))
                .build();

            server = new OpcUaServer(serverConfig);
            
            // 创建命名空间
            var namespace = server.getNamespaceTable().addUri(config.getOpcuaServer().getNamespaceUri());
            
            logger.info("OPC UA Server initialized with namespace: {}", namespace);
            
        } catch (Exception e) {
            logger.error("Failed to initialize OPC UA server", e);
            throw new RuntimeException("OPC UA server initialization failed", e);
        }
    }

    /**
     * 启动OPC UA服务器
     */
    public void startServer() {
        try {
            server.startup().get();
            logger.info("OPC UA Server started successfully");
        } catch (Exception e) {
            logger.error("Failed to start OPC UA server", e);
            throw new RuntimeException("Failed to start OPC UA server", e);
        }
    }

    /**
     * 停止OPC UA服务器
     */
    public void stopServer() {
        try {
            if (server != null) {
                server.shutdown().get();
                logger.info("OPC UA Server stopped successfully");
            }
        } catch (Exception e) {
            logger.error("Failed to stop OPC UA server", e);
        }
    }

    /**
     * 创建数据节点
     */
    public void createDataNode(String opcuaPath, String pointName, Object initialValue, String dataType) {
        try {
            String fullPath = opcuaPath + "/" + pointName;
            NodeId nodeId = createNodeId(fullPath);
            
            // 根据数据类型确定变量类型
            Variant variant = convertToVariant(initialValue, dataType);
            
            // 在OPC UA服务器中创建变量节点
            // 这里需要根据实际的Milo API实现具体的节点创建逻辑
            // 由于Milo的API比较复杂，这里提供一个简化的实现框架
            
            nodeIdCache.put(fullPath, nodeId);
            
            logger.debug("Created OPC UA node: {} -> {}", fullPath, nodeId);
            
        } catch (Exception e) {
            logger.error("Failed to create OPC UA node for {}/{}", opcuaPath, pointName, e);
        }
    }

    /**
     * 更新数据节点的值
     */
    public void updateValue(String opcuaPath, String pointName, Object value) {
        try {
            String fullPath = opcuaPath + "/" + pointName;
            NodeId nodeId = nodeIdCache.get(fullPath);
            
            if (nodeId == null) {
                logger.warn("Node not found for path: {}", fullPath);
                return;
            }

            // 这里需要实现实际的值更新逻辑
            // 由于Milo的API复杂性，这里提供一个简化的实现框架
            logger.debug("Updated OPC UA value: {} = {}", fullPath, value);
            
        } catch (Exception e) {
            logger.error("Failed to update OPC UA value for {}/{}: {}", opcuaPath, pointName, value, e);
        }
    }

    /**
     * 读取数据节点的值
     */
    public Object readValue(String opcuaPath, String pointName) {
        try {
            String fullPath = opcuaPath + "/" + pointName;
            NodeId nodeId = nodeIdCache.get(fullPath);
            
            if (nodeId == null) {
                logger.warn("Node not found for path: {}", fullPath);
                return null;
            }

            // 这里需要实现实际的值读取逻辑
            // 返回模拟值
            return null;
            
        } catch (Exception e) {
            logger.error("Failed to read OPC UA value for {}/{}", opcuaPath, pointName, e);
            return null;
        }
    }

    /**
     * 创建NodeId
     */
    private NodeId createNodeId(String path) {
        // 简化的NodeId生成逻辑，实际应用中可能需要更复杂的算法
        int hash = Math.abs(path.hashCode());
        return new NodeId(1, hash); // 使用命名空间1
    }

    /**
     * 将Java对象转换为OPC UA Variant
     */
    private Variant convertToVariant(Object value, String dataType) {
        if (value == null) {
            return new Variant(null);
        }

        try {
            switch (dataType.toUpperCase()) {
                case "DOUBLE":
                case "FLOAT":
                    return new Variant(((Number) value).doubleValue());
                case "INTEGER":
                case "INT":
                    return new Variant(((Number) value).intValue());
                case "LONG":
                    return new Variant(((Number) value).longValue());
                case "BOOLEAN":
                    return new Variant((Boolean) value);
                case "STRING":
                    return new Variant(value.toString());
                default:
                    return new Variant(value.toString());
            }
        } catch (Exception e) {
            logger.warn("Error converting value to variant: {}", e.getMessage());
            return new Variant(value.toString());
        }
    }

    /**
     * 获取服务器状态
     */
    public Map<String, Object> getServerStatus() {
        Map<String, Object> status = new ConcurrentHashMap<>();
        
        if (server != null) {
            status.put("running", true);
            status.put("nodeCount", nodeIdCache.size());
            String endpointUrl = "Not configured";
            if (server != null && server.getConfig() != null) {
                Set<EndpointConfiguration> endpoints = server.getConfig().getEndpoints();
                if (endpoints != null && !endpoints.isEmpty()) {
                    ArrayList<EndpointConfiguration> list = new ArrayList<>(endpoints);
                    endpointUrl = list.get(0).getEndpointUrl();
                }
            }
            status.put("endpointUrl", server.getConfig().getEndpoints().isEmpty() ?
                "Not configured" : endpointUrl);
        } else {
            status.put("running", false);
            status.put("nodeCount", 0);
            status.put("endpointUrl", "Server not initialized");
        }
        
        return status;
    }

//    @PreDestroy
//    public void cleanup() {
//        stopServer();
//        nodeIdCache.clear();
//        logger.info("OPC UA Service cleanup completed");
//    }

    /**
     * Spring 提供了 DisposableBean 接口，我们可以通过 destroy() 方法处理资源清理：
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        stopServer();
        nodeIdCache.clear();
        logger.info("OPC UA Service cleanup completed");
    }


}